<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Char Movie</title>
</head>

<body>
  <h1>Sync Video</h1>
  <video id="video" src="./movie.mp4" loop controls onplay="syncVideo()" onended="stopSyncVideo()" height="200"
    width="350"></video>
  <canvas id="canvas" width="350" height="200"></canvas>
  <canvas id="canvas_gray" width="350" height="200"></canvas>
  <pre id="stage"> </pre>

  <script>
    const video = document.getElementById("video");
    const canvas = document.getElementById("canvas");
    const canvasGray = document.getElementById("canvas_gray");
    const stage = document.getElementById("stage");
    const canvasCtx = canvas.getContext("2d");
    const canvasGrayCtx = canvasGray.getContext("2d");

    var rafId;
    function syncVideo() {
      const step = () => {
        canvasCtx.drawImage(video, 0, 0, video.width, video.height);
        const imageData = canvasCtx.getImageData(0, 0, video.width, video.height);
        canvasGrayCtx.putImageData(grayImageData(imageData), 0, 0);
        fillStageByText(symbolizeAsText(symbolizeImageData(imageData)));

        rafId = requestAnimationFrame(step);
      };
      rafId = requestAnimationFrame(step);
    }
    function stopSyncVideo() {
      cancelAnimationFrame(rafId);
    }

    function grayImageData(imageData) {
      const { data, width, height } = imageData;
      const _data = new Uint8ClampedArray(height * width * 4);
      for (let i = 0; i < data.length; i += 4) {
        const [Red, Green, Blue, Alpha] = data.slice(i, i + 4);
        const gray = Red * 0.3 + Green * 0.59 + Blue * 0.11;
        _data[i] = gray;
        _data[i + 1] = gray;
        _data[i + 2] = gray;
        _data[i + 3] = Alpha;
      }

      return new ImageData(_data, width, height);
    }

    function symbolizeImageData(imageData) {
      const { data, width, height } = imageData;
      const _data = new Uint8ClampedArray(height * width);
      for (let i = 0; i < data.length; i += 4) {
        const [Red, Green, Blue, Alpha] = data.slice(i, i + 4);
        const gray = Red * 0.3 + Green * 0.59 + Blue * 0.11;
        _data[(i / 4) >>> 0] = gray;
      }

      return { data: _data, width, height };
    }

    function byteScale(range) {
      return (v) => range[(v / 256 * range.length) >>> 0];
    }

    function symbolizeAsText(symbolizedImageData,
      charset = ['B', '$', '@', '&', 'M', '#', '%', 'H', '=', 'I', '+', ':', '-', '.']
    ) {
      const scaler = byteScale(charset);
      const { data, width, height } = symbolizedImageData;
      let str = '';
      for (let i = 0; i < data.length; i++) {
        const byte = data[i];
        if (i % width === 0) {
          str += '\n';
        }
        str += scaler(byte);
      }

      return str;
    }

    function fillStageByText(text) {
      stage.innerText = text;
    }

  </script>
</body>

</html>
